/* A sample, extra-simple block driver. Updated for kernel 2.6.31.
 *
 * (C) 2003 Eklektix, Inc.
 * (C) 2010 Pat Patterson <pat at superpat dot com>
 * Redistributable under the terms of the GNU GPL.
 */

#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>

#include <linux/kernel.h> /* printk() */
#include <linux/fs.h>     /* everything... */
#include <linux/errno.h>  /* error codes */
#include <linux/types.h>  /* size_t */
#include <linux/vmalloc.h>
#include <linux/genhd.h>
#include <linux/blkdev.h>
#include <linux/hdreg.h>
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/workqueue.h>
#include "block_storage.h"
#include "shard.h"

MODULE_LICENSE("Dual BSD/GPL");
static char *Version = "1.4";

static int major_num = 0;
module_param(major_num, int, 0);
static int logical_block_size = 512;
module_param(logical_block_size, int, 0);
static int nsectors = 96*1024/512*1024*1024; /* How big the drive is */
module_param(nsectors, int, 0);

/*
 * We can tweak our hardware sector size, but the kernel talks to us
 * in terms of small sectors, always.
 */
#define KERNEL_SECTOR_SIZE 512

block_storage_t *storage=NULL;

/*
 * Our request queue.
 */
static struct request_queue *Queue;

/*
 * The internal representation of our device.
 */
static struct sbd_device {
	unsigned long size;
	spinlock_t lock;
	struct gendisk *gd;
} Device;

/*static struct req_info{
	struct request *req;
	char *buf;
	int last;
};*/

struct req_info{
	struct sbd_device *dev;
	sector_t sector;
        unsigned long nsect;
	char *buffer;
	int write;
	struct request* req;
	struct work_struct work;
};


void print_buffer(char *buf){
    if(DEBUG_SHARD)	
    printk("%d%d%d%d\n", buf[0], buf[1], buf[2], buf[3]);
}

int queue_running = 1;

//ret: 1 queue is full, 0 queue is not full.
int __check_queue_full(){
	return 0;
//	printk("check full %d\n", get_outstanding_count(storage));
     	if(OUTSTANDING_LIMIT-get_outstanding_count(storage)<=64){
            printk("storage queue full\n");
            blk_stop_queue(Queue);
	    queue_running = 0;
            return 1;
     	}
	return 0;
}

void check_queue_available(){
	return;
	if(queue_running)
		return;
	int outstanding = get_outstanding_count(storage);
        if(outstanding<OUTSTANDING_LIMIT/5){
	    unsigned long flags;
            printk("storage queue available\n");
	    spin_lock_irqsave(Queue->queue_lock, flags);
            blk_start_queue(Queue);
	    spin_unlock_irqrestore(Queue->queue_lock, flags);
            queue_running = 1;
        }
}


/*
 * Handle an I/O request.
 */

int writeCount = 0;

void write_completion(int rc, const void *data){
    struct request* req = data;
    check_queue_available();
    if(req!=NULL){
	//if(DEBUG_SHARD)
	printk("write completion %d %d\n", req, req->buffer);
	//spin_lock_irq(&Device.lock);
        if(!blk_end_request_cur(req, 0)){
	    if(DEBUG_SHARD)
	    printk("write completed\n");
	}
	
	//spin_unlock_irq(&Device.lock);
    }
}

void read_completion(int rc, const char *value, int value_len,
        const void *data){
    struct request *req = data;
    check_queue_available();
    if(req!=NULL){
	//if(DEBUG_SHARD)
	printk("read complete %d\n", req);
	print_buffer(value);
	//sector_t sector = blk_rq_pos(req);
        //unsigned long nsect = blk_rq_sectors(req);

	/*if(sector==2&&nsect==2){
	    printk("read super block\n");
	    memcpy(req->buffer, value+1024, 1024);
	}
	else*/
	    memcpy(req->buffer, value, value_len);
	kfree(value);
	if(!blk_end_request_cur(req, 0)){
	    if(DEBUG_SHARD)
	    printk("read completed\n");
	}
    }
}

void sbd_transfer2(struct work_struct *work){
	struct req_info *info = container_of(work, struct req_info, work);
	struct sbd_device *dev = info->dev;
	sector_t sector = info->sector;
        unsigned long nsect = info->nsect;
	char *buffer = info->buffer;
	int write = info->write;
	struct request* req = info-> req;
        unsigned long offset = sector * logical_block_size;
        unsigned long nbytes = nsect * logical_block_size;
        int i;
        //if(DEBUG_SHARD)
        printk("sbd_transfer sector nsect %ld %ld %d, buffer =%d, req=%d\n", offset, nbytes, write, buffer, req);
        if(write)
            print_buffer(buffer);

        if(nbytes!=4096){
             printk("Unrecogonized request\n");
        }

        if(storage!=NULL){
            if(write)
                write_block(storage, sector/8, buffer, nsect*512, write_completion, req);
            else
                read_block(storage, sector/8, read_completion, req);
        }
        else
            printk("storage is null\n");
	kfree(info);
}


static void sbd_transfer(struct sbd_device *dev, sector_t sector,
		unsigned long nsect, char *buffer, int write, struct request* req) {
	struct req_info* info = (struct req_info*)kmalloc(sizeof(struct req_info), GFP_ATOMIC);
	if(info==NULL){
	    printk("kmalloc failed\n");
	    return;
	}
	info->dev = dev;
	info->sector = sector;
	info->nsect = nsect;
	info->buffer = buffer;
	info->write = write;
	info->req = req;
	INIT_WORK(&info->work, sbd_transfer2);
	schedule_work(&info->work);
	/*unsigned long offset = sector * logical_block_size;
	unsigned long nbytes = nsect * logical_block_size;
	int i;
	if(DEBUG_SHARD)
	printk("sbd_transfer sector nsect %ld %ld %d, buffer =%d, req=%d\n", offset, nbytes, write, buffer, req);
	if(write)
	    print_buffer(buffer);

	if(nbytes!=4096){
	     printk("Unrecogonized request\n");
	}
	
	if(storage!=NULL){
	    if(write)
            	write_block(storage, sector/8, buffer, nsect*512, write_completion, req);
	    else
	    	read_block(storage, sector/8, read_completion, req);
	}
	else
	    printk("storage is null\n");*/

}

static void sbd_request(struct request_queue *q) {
	struct request *req;

	if(DEBUG_SHARD)
	printk("sbd request start %d\n", q);

	if(__check_queue_full(storage)){
	    return;
	}

	req = blk_fetch_request(q);
	while (req != NULL) {
		// blk_fs_request() was removed in 2.6.36 - many thanks to
		// Christian Paro for the heads up and fix...
		//if (!blk_fs_request(req)) {
		if (req == NULL || (req->cmd_type != REQ_TYPE_FS)) {
			printk (KERN_NOTICE "Skip non-CMD request\n");
			__blk_end_request_all(req, -EIO);
			continue;
		}


		sector_t sector = blk_rq_pos(req);
		unsigned long nsect = blk_rq_sectors(req);
		char *buffer = req->buffer;
		int write = rq_data_dir(req);
		if(nsect%8!=0)
		     printk("abnormal %d\n", nsect);

		if(nsect!=8)
		     printk("large %d\n", nsect);
		
		int i;
		for(i=0; i<nsect/8; i++){
			sbd_transfer(&Device, sector+i*8, 8, buffer+i*4096, write, req);
		}
		
		
		/*sbd_transfer(&Device, sector, nsect,
                                buffer, write, req);*/
		if(__check_queue_full(storage)){
            		return;
        	}

		req = blk_fetch_request(q);

		/*unsigned long used = 0;
		while(used<nsect){
		     unsigned long next = 8;
		     if(nsect-used<8)
			next = nsect - used;
		     sbd_transfer(&Device, sector+used, next,
				buffer-512*used, write, req);
		     printk("%d %d\n",buffer-512*used, req->buffer);
		     print_buffer(req->buffer);
		     __blk_end_request_cur(req, 0);
		     used+=next;
		}*/



/*		sbd_transfer(&Device, blk_rq_pos(req), blk_rq_cur_sectors(req),
				req->buffer, rq_data_dir(req), req);
		if ( ! __blk_end_request_cur(req, 0) ) {
			req = blk_fetch_request(q);
		}*/
	}
	//printk("sbd_request end\n");
}

/*
 * The HDIO_GETGEO ioctl is handled in blkdev_ioctl(), which
 * calls this. We need to implement getgeo, since we can't
 * use tools such as fdisk to partition the drive otherwise.
 */
int sbd_getgeo(struct block_device * block_device, struct hd_geometry * geo) {
	long size;

	/* We have no real geometry, of course, so make something up. */
	size = Device.size * (logical_block_size / KERNEL_SECTOR_SIZE);
	geo->cylinders = (size & ~0x3f) >> 6;
	geo->heads = 4;
	geo->sectors = 16;
	geo->start = 0;
	return 0;
}

/*
 * The device operations structure.
 */
static struct block_device_operations sbd_ops = {
		.owner  = THIS_MODULE,
		.getgeo = sbd_getgeo
};

static int __init sbd_init(void) {
	char data[512];
	/*
	 * Set up our internal device.
	 */
	printk("start initializing storage\n");
        storage = block_storage_init("application1:2181,application2:2181,application3:2181", 1);
	msleep(3000);
	write_block(storage, -1, data, 4096, write_completion, NULL);
	msleep(30000);
	/*if(storage!=NULL){
            for(i=0; i<10; i++)
                write_block(storage, i, data, 4096, write_completion,0);
        }
        else
            printk("storage is null\n");*/
        printk("end initializing storage\n");

	Device.size = nsectors * logical_block_size;
	spin_lock_init(&Device.lock);
	/*Device.data = vmalloc(Device.size);
	if (Device.data == NULL)
		return -ENOMEM;*/
	/*
	 * Get a request queue.
	 */
	Queue = blk_init_queue(sbd_request, &Device.lock);
	if (Queue == NULL)
		goto out;
	blk_queue_logical_block_size(Queue, 4096);//logical_block_size);
	blk_queue_max_hw_sectors(Queue, 8*256);
	blk_queue_max_segments(Queue, 1);
	//blk_queue_physical_block_size(Queue, 4096);
	/*
	 * Get registered.
	 */
	major_num = register_blkdev(major_num, "sbd");
	if (major_num <= 0) {
		printk(KERN_WARNING "sbd: unable to get major number\n");
		goto out;
	}
	/*
	 * And the gendisk structure.
	 */
	Device.gd = alloc_disk(16);
	if (!Device.gd)
		goto out_unregister;
	Device.gd->major = major_num;
	Device.gd->first_minor = 0;
	Device.gd->fops = &sbd_ops;
	Device.gd->private_data = &Device;
	strcpy(Device.gd->disk_name, "sbd0");
	set_capacity(Device.gd, nsectors);
	Device.gd->queue = Queue;
	add_disk(Device.gd);


	return 0;

out_unregister:
	unregister_blkdev(major_num, "sbd");
out:
	//vfree(Device.data);
	return -ENOMEM;
}

static void __exit sbd_exit(void)
{
	del_gendisk(Device.gd);
	put_disk(Device.gd);
	unregister_blkdev(major_num, "sbd");
	blk_cleanup_queue(Queue);
	//vfree(Device.data);
	if(storage!=NULL)
	    close(storage);
}

module_init(sbd_init);
module_exit(sbd_exit);

